package com.yupi.oj.judge.service.impl;

import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.yupi.oj.codeBox.model.requesst.ExecuteRequest;
import com.yupi.oj.codeBox.model.response.ExecuteResponse;
import com.yupi.oj.codeBox.service.ExecuteCodeService;
import com.yupi.oj.common.ErrorCode;
import com.yupi.oj.component.CodeSandBoxComponent;
import com.yupi.oj.exception.BusinessException;
import com.yupi.oj.judge.model.dto.JudgeRequest;
import com.yupi.oj.judge.service.JudgeService;
import com.yupi.oj.model.dto.question.ExampleDTO;
import com.yupi.oj.model.entity.Question;
import com.yupi.oj.model.entity.QuestionSubmitLog;
import com.yupi.oj.model.entity.User;
import com.yupi.oj.model.enums.ExecuteStatusEnum;
import com.yupi.oj.model.enums.SubmitStatusEnum;
import com.yupi.oj.service.QuestionService;
import com.yupi.oj.service.QuestionSubmitLogService;
import com.yupi.oj.service.UserService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.servlet.http.HttpServletRequest;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @author yanghaifeng
 * @date 2024/4/23 11:33
 */
@Slf4j
@Service
public class JudgeServiceImpl implements JudgeService {

    @Autowired
    private QuestionService questionService;

    @Autowired
    private QuestionSubmitLogService questionSubmitLogService;

    @Autowired
    private UserService userService;

    @Autowired
    private ExecuteCodeService executeCodeService;


    @Override
    public void judge(JudgeRequest judgeRequest, HttpServletRequest request) {
        // 获取用户信息
        User loginUser = userService.getLoginUser(request);

        Long questionId = judgeRequest.getQuestionId();
        String code = judgeRequest.getCode();
        String language = judgeRequest.getLanguage();
        // 请参校验
        if (StrUtil.hasBlank(code, language)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        if (ObjectUtil.isNull(questionId)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 校验题目是否存在
        Question question = questionService.getById(questionId);
        if (ObjectUtil.isNull(question)) {
            throw new BusinessException(ErrorCode.NOT_FOUND_ERROR, "题目不存在");
        }
        Integer memoryLimit = question.getMemoryLimit();
        Integer timeLimit = question.getTimeLimit();
        if (ObjectUtil.hasNull(memoryLimit, timeLimit)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目内存限制或者时间限制为设定");
        }

        // 保存提交记录
        QuestionSubmitLog submitLog = new QuestionSubmitLog();
        submitLog.setQuestionId(questionId);
        submitLog.setSubmitLanguage(language);
        submitLog.setCodeDescription(code);
        submitLog.setCodeLength((double) this.calculateCodeSize(code));
        submitLog.setSubmitStatus(SubmitStatusEnum.COMPILE.getValue());
        submitLog.setCreateBy(loginUser.getUserAccount());
        submitLog.setUpdateBy(loginUser.getUserAccount());
        questionSubmitLogService.save(submitLog);
        Long logId = submitLog.getId();


        // 查询题目信息，封装数据请求代码沙箱
        ExecuteRequest executeRequest = new ExecuteRequest();
        String example = question.getExample();
        List<ExampleDTO> exampleDTOList = JSONUtil.toList(example, ExampleDTO.class);
        if (ObjectUtil.isEmpty(exampleDTOList)) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "题目未设置样例");
        }
        List<String> inputExampleList = exampleDTOList.stream().map(ExampleDTO::getInput).collect(Collectors.toList());
        List<String> outputExampleList = exampleDTOList.stream().map(ExampleDTO::getOutput).collect(Collectors.toList());
        executeRequest.setInputExample(inputExampleList);
        executeRequest.setLanguage(language);
        executeRequest.setCode(code);
        ExecuteResponse executeResponse = CodeSandBoxComponent.callCodeBox(executeRequest);
        log.info("code sand box result: {}",JSONUtil.toJsonStr(executeResponse));

        // 获取沙箱响应信息，与预设信息对比
        judgeQuestionResult(question, outputExampleList, logId, executeResponse);
    }


    /**
     * 根据代码沙箱进行判题
     *
     * @param question          问题
     * @param outputExampleList 问题预设输出结果
     * @param logId             提交记录 Id
     * @param executeResponse   代码沙箱执行结果
     */
    private void judgeQuestionResult(Question question, List<String> outputExampleList, Long logId, ExecuteResponse executeResponse) {
        Integer timeLimit = question.getTimeLimit();
        Integer memoryLimit = question.getMemoryLimit();

        String executeStatus = executeResponse.getExecuteStatus();
        Integer executeTime = executeResponse.getExecuteTime();
        Integer executeMemory = executeResponse.getExecuteMemory();
        List<String> executeResult = executeResponse.getExecuteResult();
        String errorInfo = executeResponse.getErrorInfo();
        // 如果答案错误，直接修改数据，保存错误信息；否则才进行输出结果的对比
        QuestionSubmitLog questionSubmitLog = questionSubmitLogService.getById(logId);
        if (ExecuteStatusEnum.FAILED.getKey().equals(executeStatus)) {
            questionSubmitLog.setSubmitStatus(SubmitStatusEnum.ERROR.getValue());
            questionSubmitLog.setErrorInfo(errorInfo);
        } else {
            questionSubmitLog.setSubmitStatus(SubmitStatusEnum.CORRECT.getValue());
            // 对比输出结果
            for (String outputExample : outputExampleList) {
                int index = executeResult.indexOf(outputExample);
                if (index == -1) {
                    // 代码执行输出结果不正确
                    questionSubmitLog.setSubmitStatus(SubmitStatusEnum.ERROR.getValue());
                    break;
                }
            }
        }
        // 校验时间和内存是否超出限制
        if (executeTime > timeLimit || executeMemory > memoryLimit) {
            questionSubmitLog.setSubmitStatus(SubmitStatusEnum.ERROR.getValue());
            questionSubmitLog.setErrorInfo("内存或者时间超出题目设置");
        }
        questionSubmitLog.setMemoryConsume(executeMemory);
        questionSubmitLog.setExecuteTime(executeTime);
        questionSubmitLogService.updateById(questionSubmitLog);
    }

    /**
     * 计算代码字节大小
     *
     * @param code 代码
     * @return 字节大小
     */
    private int calculateCodeSize(String code) {
        byte[] bytes = code.getBytes(StandardCharsets.UTF_8);
        return bytes.length;
    }
}
